(ns clojure-simple-nio.core
  (:import (java.nio ByteBuffer)
           (java.nio.channels Selector SelectionKey ServerSocketChannel SocketChannel)
           (java.net ServerSocket InetSocketAddress)
           (java.nio.charset Charset)))

(def ^:dynamic *buffer* (ByteBuffer/allocate 16384))

(defn selector [^ServerSocketChannel server-socket-channel]
  "开启一个选择器"
  (let [^Selector selector (Selector/open)]
    ;; 并且把服务器通道注册到选择器中
    (.register server-socket-channel selector SelectionKey/OP_ACCEPT)
    selector))

(defn setup [port]
  "初始化"
  ;; 打开一个服务端套接字通道
  (let [^ServerSocketChannel server-socket-channel (ServerSocketChannel/open)
        ;; 配置阻塞模式
        _ (.configureBlocking server-socket-channel false)
        ;; 获取服务器socket
        ^ServerSocket server-socket (.socket server-socket-channel)
        ;; 设置绑定端口
        inet-socket-address (InetSocketAddress. port)]
    (.bind server-socket inet-socket-address)
    ;; 把服务器通道注册到选择器中
    ;; 返回 选择器和服务器套接字
    [(selector server-socket-channel)
     server-socket]))

(defn state= [state channel]
  "判断通道当前处理的事件"
  (= (bit-and (.readyOps channel) state) state))

(defn buffer->string
  "缓冲转换为字符串"
  ([byte-buffer]
   (buffer->string byte-buffer (Charset/defaultCharset)))
  ([byte-buffer charset]
   (.toString (.decode charset byte-buffer))))

(defn string->buffer
  "字符串转换为缓冲"
  ([string]
   (string->buffer string (Charset/defaultCharset)))
  ([string charset]
   (.encode charset string)))

(defn accept-connection [^ServerSocket server-socket ^Selector selector]
  "获取通道中的socket 并且注册为读事件"
  ;; 获取通道中的channel
  (let [^SocketChannel channel (-> server-socket (.accept) (.getChannel))]
    (println "连接来自:" channel)
    ;; 获取客户端ip地址
    (println "连接来自:" (.. channel (socket) (getInetAddress) (getHostAddress)))
    ;; 注册通道事件
    (doto channel
      (.configureBlocking false)
      (.register selector SelectionKey/OP_READ))))

(defn read-socket [selected-key]
  "读取通道"
  (let [^SocketChannel socket-channel (.channel selected-key)]
    ;; 清理buffer
    (.clear *buffer*)
    ;; 读取内容到通道中
    (.read socket-channel *buffer*)
    (.flip *buffer*)
    (if (= (.limit *buffer*) 0)
      (do
        (println "丢失连接:" socket-channel)
        ;; 关闭通道
        (.cancel selected-key)
        (.close (.socket socket-channel)))
      (.write socket-channel *buffer*))))

(defn react [selector server-socket]
  "反应堆"
  (while true
    ;; 选择有IO的通道
    (when (> (.select selector) 0)
      ;; 返回已经选择的建 就是那些有io需求的通道
      (let [selected-keys (.selectedKeys selector)]
        ;; 遍历通道 逐个判断
        (doseq [k selected-keys]
          (condp state= k
            ;; 有接受请求
            SelectionKey/OP_ACCEPT
            (accept-connection server-socket selector)
            ;; 有读请求
            SelectionKey/OP_READ
            (read-socket k)))
        (.clear selected-keys)))))

(defn run []
  (apply react (setup 2323)))

(defn -main []
  (run))